﻿#if UNITY_EDITOR

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text;
using System;
using System.Linq;
using System.Reflection;

namespace BF
{
    /// <summary>
    /// 配置文件转化，仅支持CSV
    /// </summary>
    public static class ConfigConvert
    {
        /// <summary>
        /// 忽略字段标志
        /// </summary>
        public const string IgnoreField = "!";

        /// <summary>
        /// CSV源数据文件目录
        /// </summary>
        public static string originDir = Application.dataPath + "/__BF/Editor/Config/CSV/";
        /// <summary>
        /// 生成的代码保存目录
        /// </summary>
        public static string scriptOutDir = Application.dataPath + "/__BF/Scripts/Config/Bean/";
        /// <summary>
        /// 生成的二进制数据文件保存目录
        /// </summary>
        public static string dataOutDir = Application.dataPath + "/__BF/Resources/Config/Bean/";
        /// <summary>
        /// 变体配置文件路径
        /// </summary>
        public static string variantPath = Application.dataPath + "/__BF/Editor/Config/VariantTypes.txt";

        /// <summary>
        /// 需要使用变变体的类型
        /// 同一个数据类型有多个配置文件
        /// </summary>
        public static readonly List<string> variantTypes;

        static ConfigConvert()
        {
            Check();

            variantTypes = File.ReadAllLines(Application.dataPath + "/__BF/Editor/Config/VariantTypes.txt")
              .Where(line => !string.IsNullOrEmpty(line) && !line.Contains("//"))
              .ToList();
        }


        public static void Check()
        {
            CheckDirector(originDir);
            CheckDirector(scriptOutDir);
            CheckDirector(dataOutDir);


            if (!File.Exists(variantPath))
            {
                File.WriteAllText(variantPath, "//需要使用变体的类型,同一个数据类型有多个配置文件");
            }

            string notePath = originDir + "!__note.txt";
            if (!File.Exists(notePath))
            {
                File.WriteAllText(notePath, @"
注意事项：

有中文的文件，请将编码格式转为UTF-8，不然将会有乱码

一.CSV格式
1. csv第1行第列为整个表的注释
2. csv第2行为每一列注释
3. csv第3行为每一列对象变量名称
4. csv第4行为每一列对应的变量类型，仅支持C#简单类型：
    string,byte,sbyte,short,ushort,int,uint,long,ulong,float,double,decimal,bool,char,DateTime
    DateTime格式如：2019-06-01 00:00:00
5. 数组请在变量类型后加上[]，例如int[],string[]
6. csv第5行开始到末尾是具体数据，若数据是数组请用 '|'分隔

二.使用步骤
1. 修改ConfigConvert代码中的originDir,scriptOutDir,dataOutDir目录路径
2. 将csv文件放到originDir路径中
3. 菜单栏Tools/ConfigConvert/根据CSV生成Config代码
4. 菜单栏Tools/ConfigConvert/根据CSV生成二进制数据文件

三.示例如下：
玩家等级表,,,
等级,需要经验,BonusCash奖励数量,钻石奖励数量
level,needExp,awardsBonusCash,awardsDiamond
int,int,int,int
1,65,0,2
2,100,0,3
");
            }
        }

        private static void CheckDirector(string dir)
        {
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
            if (!File.Exists(dir + ".gitkeep"))
            {
                File.Create(dir + ".gitkeep");
            }
        }


        /// <summary>
        /// 变体名称检查
        /// </summary>
        private static string VariantTypesCheck(string name)
        {
            foreach (string variantType in variantTypes)
            {
                if (name.StartsWith(variantType))
                {
                    return variantType;
                }
            }
            return name;
        }

        /// <summary>
        /// 根据CSV生成Config代码
        /// </summary>
        [MenuItem("BF/ConfigConvert/根据CSV生成Config代码")]
        public static void ConvertToScripts()
        {
            DelectDir(scriptOutDir);
            string[] files = Directory.GetFiles(originDir, "*.csv");
            foreach (string filePath in files)
            {
                EncodingUtils.ConvertFileToUTF8(filePath);
                if (filePath.Contains(IgnoreField))
                {
                    continue;
                }
                ConvertToScripts(filePath);
            }
            AssetDatabase.Refresh();
            Debug.Log($"[ConfigConvert] ConvertToScripts finish: {originDir}");
        }

        public static void DelectDir(string srcPath)
        {
            DirectoryInfo dir = new DirectoryInfo(srcPath);
            FileSystemInfo[] fileinfo = dir.GetFileSystemInfos();  //返回目录中所有文件和子目录
            foreach (FileSystemInfo i in fileinfo)
            {
                if (i is DirectoryInfo)            //判断是否文件夹
                {
                    DirectoryInfo subdir = new DirectoryInfo(i.FullName);
                    subdir.Delete(true);          //删除子目录和文件
                }
                else if (!i.Name.Contains(".git"))
                {
                    File.Delete(i.FullName);      //删除指定文件
                }
            }
        }

        /// <summary>
        /// 根据CSV生成Config代码
        /// </summary>
        public static void ConvertToScripts(string filePath)
        {
            string name = Path.GetFileNameWithoutExtension(filePath);
            name = VariantTypesCheck(name);
            name += "Bean";
            List<List<string>> lines = CsvUtil.LoadCsv(File.ReadAllText(filePath));
            if (lines == null || lines.Count == 0)
            {
                return;
            }
            string classComment = lines[0][0]; //第0行第0列为整个类的注释
            lines.RemoveAt(0);

            List<List<string>> columns = CsvUtil.Row2Column(lines);
            for (int i = columns.Count - 1; i >= 0; --i)
            {
                if (string.IsNullOrEmpty(columns[i][1])
                    || columns[i][1].Contains(IgnoreField))
                {
                    columns.RemoveAt(i);
                }
            }

            StringBuilder sb = new StringBuilder();
            sb.Append("using BF;\n");
            sb.Append("using System;\n\n");
            sb.Append($"/// <summary>\n");
            sb.Append($"/// {classComment}\n");
            sb.Append($"/// </summary>\n");
            sb.Append("[Serializable]\n");
            sb.Append($"public class {name} : IConfigBean\n"); //类名
            sb.Append("{\n");

            /********变量********/
            for (int i = 0; i < columns.Count; ++i)
            {
                sb.Append($"    /// <summary>\n");
                sb.Append($"    /// {columns[i][0]}\n");
                sb.Append($"    /// </summary>\n");
                sb.Append($"    public readonly {columns[i][2]} {columns[i][1]};\n");
            }
            /********变量********/

            /********构造方法无参数********/
            sb.Append($"\n    public {name}()");
            sb.Append("\n    {");
            sb.Append("\n    }\n");
            /********构造方法无参数********/

            /********构造方法有参数********/
            sb.Append($"\n    public {name}(");
            for (int i = 0; i < columns.Count; ++i)
            {
                if (columns[i][1].Contains(IgnoreField))
                {
                    continue;
                }
                sb.Append($"{(i == 0 ? "" : ", ")}{columns[i][2]} {columns[i][1]}");
            }
            sb.Append($")");
            sb.Append("\n    {");
            for (int i = 0; i < columns.Count; ++i)
            {
                if (columns[i][1].Contains(IgnoreField))
                {
                    continue;
                }
                sb.Append($"\n        this.{columns[i][1]} = {columns[i][1]};");
            }
            sb.Append("\n    }\n");
            /********构造方法有参数********/

            sb.Append("}");
            StreamWriter sw = new StreamWriter($"{scriptOutDir}{name}.cs");
            sw.Write(sb);
            sw.Close();

            Debug.Log($"[ConfigConvert] ConvertToScripts {name} finish");
        }


        /// <summary>
        /// 根据CSV生成二进制数据文件
        /// </summary>
        [MenuItem("BF/ConfigConvert/根据CSV生成二进制数据文件")]
        public static void ConvertToData()
        {
            DelectDir(dataOutDir);
            string[] files = Directory.GetFiles(originDir, "*.csv");
            foreach (string filePath in files)
            {
                if (filePath.Contains(IgnoreField))
                {
                    continue;
                }
                ConvertToData(filePath);
            }
            AssetDatabase.Refresh();
            Debug.Log($"[ConfigConvert] ConvertToData finish: {dataOutDir}");
        }

        /// <summary>
        /// 根据CSV生成二进制数据文件
        /// </summary>
        public static void ConvertToData(string filePath)
        {
            string fileName = Path.GetFileNameWithoutExtension(filePath);
            string beanTypeName = VariantTypesCheck(fileName) + "Bean";

            fileName += "Bean";

            List<List<string>> lines = CsvUtil.LoadCsv(File.ReadAllText(filePath));
            if (lines == null || lines.Count == 0)
            {
                return;
            }
            try
            {
                Type beanType = GetType(beanTypeName);

                object objList = Activator.CreateInstance(typeof(List<>).MakeGenericType(beanType));
                MethodInfo addMethod = objList.GetType().GetMethod("Add");
                for (int row = 4; row < lines.Count; ++row) //索引第4行开始才是具体数据
                {
                    List<object> fileds = new List<object>();
                    for (int col = 0; col < lines[2].Count; ++col)
                    {
                        string filedName = lines[2][col];
                        if (string.IsNullOrEmpty(filedName)
                            || filedName.Contains(IgnoreField))
                        {
                            continue;
                        }
                        string typeName = lines[3][col]; //第3行是数据类型
                        string value = (lines[row].Count < col) ? "" : lines[row][col];
                        try
                        {
                            fileds.Add(ConvertFileds(typeName, value));
                        }
                        catch (Exception e)
                        {
                            Debug.LogError($"[ConvertFiled][Error] fileName:{fileName},line:{row}, row:{col}, filedName:{filedName}, typeName:{typeName}, value:{value}");
                            throw e;
                        }
                    }
                    object obj = Activator.CreateInstance(beanType, fileds.ToArray());
                    addMethod.Invoke(objList, new object[] { obj });
                }
                SaveObjectData(fileName, objList);
            }
            catch (Exception e)
            {
                Debug.LogError($"[ConvertToData][Error] fileName:{fileName}");
                Debug.LogError(e);
                Debug.LogError(e.Message);
                Debug.LogError(e.StackTrace);
            }
        }

        private static Type GetType(string typeName)
        {
            Type type = TypeUtils.FindTypeByName(typeName);

            if (type == null)
            {
                Debug.LogError($"Can't find type {typeName}");
                throw new TypeAccessException(typeName);
            }
            return type;
        }


        private static object ConvertFileds(string typeName, string value)
        {
            object obj = null;
            try
            {
                if (typeName.Contains("[]"))
                {
                    obj = ConvertFileds(typeName.Replace("[]", ""), value.Trim().Split('|'));
                }
                else
                {
                    Type type = GetType(typeName);
                    if (type.IsEnum)
                    {
                        obj = Enum.Parse(type, value);
                    }
                    else if ("bool".Equals(typeName))
                    {
                        if (string.IsNullOrEmpty(value)
                            || (value != "1" && value.ToLower() != "true"))
                        {
                            obj = false;
                        }
                        else
                        {
                            obj = true;
                        }
                    }
                    else
                    {
                        if (string.IsNullOrEmpty(value))
                        {
                            switch (typeName)
                            {
                                case "sbyte":
                                case "short":
                                case "int":
                                case "long":
                                case "byte":
                                case "ushort":
                                case "uint":
                                case "ulong":
                                case "float":
                                case "double":
                                case "decimal":
                                    value = "0";
                                    break;
                                case "DateTime":
                                    obj = new DateTime(0);
                                    return obj;
                                default:
                                    break;
                            }
                        }

                        obj = Convert.ChangeType(value, type);
                    }
                }
            }
            catch (Exception e)
            {
                Debug.LogError("-------- Convert filed error --------------");
                Debug.LogError(e);
                Debug.LogError(e.Message + "\n" + e.StackTrace);
                Debug.LogError("-------- Convert filed error --------------");
                throw e;
            }
            return obj;
        }

        /// <summary>
        /// 数组类型的字段
        /// </summary>
        private static object ConvertFileds(string typeName, string[] value)
        {
            Type type = GetType(typeName);
            Array array = Array.CreateInstance(type, value.Length);
            for (int i = 0; i < array.Length; ++i)
            {
                array.SetValue(ConvertFileds(typeName, value[i]), i);
            }
            return array;
        }


        /// <summary>
        /// 保存对象为二进制数据
        /// </summary>
        private static void SaveObjectData(string name, object objs)
        {
            //string jsonPath = $"{dataOutDir}{name}.json";
            //string jsonStr = Newtonsoft.Json.JsonConvert.SerializeObject(objs, Newtonsoft.Json.Formatting.Indented);
            //File.WriteAllText(jsonPath, jsonStr);

            string path = $"{dataOutDir}{name}.bytes";
            byte[] bytes = SerializeUtil.SerializeToBinary(objs);
            File.WriteAllBytes(path, bytes);

            Debug.Log($"[ConfigConvert] ConvertToData {name} finish");
        }
    }
}
#endif